/*
 * Copyright (c) 2011 Samuel Ventura All Rights Reserved
 */
package dev2net;

import gnu.io.CommPort;
import gnu.io.CommPortIdentifier;
import gnu.io.NoSuchPortException;
import gnu.io.PortInUseException;
import gnu.io.SerialPort;
import gnu.io.UnsupportedCommOperationException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

/**
 *
 * @author samuel
 */
class ComThread extends Thread implements ClosableThread {

    private ComConfig config;
    private ServerSocket server;
    private ClosableThread thread;

    public ComThread(ComConfig config) throws IOException {
        this.config = config;
        this.setDaemon(true);
        this.setName(config.getPortname() + "_" + config.getTcpport());
        this.server = new ServerSocket(config.getTcpport());
    }

    public ComConfig getConfig() {
        return config;
    }

    @Override
    public void run() {
        Util.out("thread start");
        try {
            while (!server.isClosed()) {
                ClosableThread thread = new ClientThread(server.accept());
                Util.close(this.thread); //close current
                this.thread = thread;
                this.thread.start();
            }
        } catch (IOException ex) {
            Util.out("ioe:" + ex.getMessage());
        } finally {
            Util.close(server);
            Util.close(thread);
            Util.out("exit thread intr:" + Thread.currentThread().isInterrupted());
        }
    }

    @Override
    public synchronized void close() {
        Util.close(server);
        Util.close(thread);
        interrupt();
        try {
            join();
        } catch (InterruptedException ex) {
            Util.out(ex.getMessage());
        }
        Util.out("closed " + getName());
    }

    private class ClientThread extends Thread implements ClosableThread {

        private Socket client;
        private CommPort comm;

        public ClientThread(Socket client) {
            this.client = client;
            this.setDaemon(true);
            this.setName(config.getPortname());
        }
        
        @Override
        public void run() {
            Util.out("thread start");
            try {
                handleClient();
            } catch (IOException ex) {
                Util.out("ioe:" + ex.getMessage());
            } finally {
                Util.close(comm);
                Util.close(client);
                Util.out("exit thread intr:" + Thread.currentThread().isInterrupted());
            }
        }

        private void handleClient() throws IOException {
            CommPortIdentifier id;

            try {
                id = CommPortIdentifier.getPortIdentifier(config.getPortname());
            } catch (NoSuchPortException ex) {
                throw new IOException("Port does not exists");
            }

            try {
                comm = id.open(this.toString(), 4000);
            } catch (PortInUseException ex) {
                if (id.isCurrentlyOwned()) {
                    throw new IOException("Port is currently in use");
                }
            }

            SerialPort serial = (SerialPort) comm;
            try {
                serial.setSerialPortParams(config.getBaudrate(), config.getDatabits(), config.getStopbits(), config.getParity());
            } catch (UnsupportedCommOperationException ex) {
                throw new IOException("Invalid port config");
            }

            InputStream cin = comm.getInputStream();
            OutputStream cout = comm.getOutputStream();
            InputStream sin = client.getInputStream();
            OutputStream sout = client.getOutputStream();
            while (!client.isClosed()) {
                if (sin.available() > 0) {
                    byte[] buf = new byte[sin.available()];
                    int c = sin.read(buf);
                    cout.write(buf, 0, c);
                    cout.flush();
                }
                if (cin.available() > 0) {
                    byte[] buf = new byte[cin.available()];
                    int c = cin.read(buf);
                    sout.write(buf, 0, c);
                    sout.flush();
                }
            }
        }

        @Override
        public synchronized void close() {
            Util.close(client);
            Util.close(comm);
            interrupt();
            try {
                join();
            } catch (InterruptedException ex) {
                Util.out(ex.getMessage());
            }
            Util.out("closed " + getName());        
        }
    }
}
